/**
 * 文件相关中间件
 *
 * Created by snowingsea on 2020/02/17.
 */
const gitUrlParse = require('git-url-parse');
const path = require('path');
const files = require('./../../models/files');
const fileElements = require('./../../models/file-elements');
const projects = require('./../../models/projects');
const projectVersions = require('./../../models/project-versions');
const projectVersionHistories = require('./../../models/project-version-histories');
const environments = require('./../../models/environments');
const rules = require('./../../models/rules');
const PhoenixError = require('./../../models/phoenix-error');
const utility = require('./../../models/utility');
const defects = require('./../../models/defects');
/**
 * 检查环境是否变化
 * @param req
 * @return {Promise}
 */
function checkEnvironmentChanged(req) {
  if (!req.$environment) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    const environmentId = req.$environment._id; // eslint-disable-line no-underscore-dangle
    environments.getEnvironment(environmentId)
      .then((environment) => {
        if (req.$environment.currentCommitId === environment.currentCommitId) {
          return Promise.resolve();
        }
        return projectVersions.updateEnvironmentChanged(environmentId, true);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 判断URL参数中文件Id是否存在
 * @param req
 * @param res
 * @param next
 */
function requireFileExist(req, res, next) {
  const { fileId } = req.params;
  if (!fileId) {
    next(new PhoenixError('invalid request', 'Missing parameters. "fileId" is required'));
    return;
  }
  files.getFile(fileId)
    .then((file) => {
      if (!file) {
        return Promise.reject(new PhoenixError('not found', 'Not found, file not found'));
      }
      req.$file = file;

      const { projectId, versionId } = file;
      const promises = [
        projects.getProject(projectId),
        projectVersions.getVersion(versionId),
      ];
      return Promise.all(promises);
    })
    .then(([project, projectVersion]) => {
      if (!projectVersion) {
        return Promise.reject(new PhoenixError('not found', 'Not found, projectVersion not found'));
      }
      if (!project) {
        return Promise.reject(new PhoenixError('not found', 'Not found, project not found'));
      }
      req.$project = project;
      req.$projectVersion = projectVersion;
      return Promise.resolve();
    })
    .then(() => {
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 需要当前文件为目录
 * @param req
 * @param res
 * @param next
 */
function requireDirectory(req, res, next) {
  let err;
  if (req.$file.kind === files.kind.file) {
    err = new PhoenixError('over range', 'The file should be directory');
  }
  next(err);
}

/**
 * 进行文件列表请求解析
 * @param req
 * @param res
 * @param next
 */
function parseFileListQuery(req, res, next) {
  ['fileIds', 'exceptFileIds'].forEach((key) => {
    req.query[key] = utility.parseArray(req.query, key);
    req.body[key] = utility.parseArray(req.body, key);
    if (!req.query[key]) {
      req.query[key] = req.body[key];
    } else if (req.body[key]) {
      req.query[key] = req.query[key].concat(req.body[key]);
    }
  });
  next();
}

/**
 * 需要当前文件不为冻结/待解析状态
 * @param req
 * @param res
 * @param next
 */
function requireFileAvailable(req, res, next) {
  let err;
  switch (req.$file.status) {
    case files.statusKind.waiting4compiling:
      err = new PhoenixError('forbidden', 'The file is frozen');
      break;
    case files.statusKind.deleting:
      err = new PhoenixError('forbidden', 'The file is deleting');
      break;
    default:
      break;
  }
  next(err);
}

/**
 * 过滤当前文件不为冻结/待解析状态
 * @param req
 * @param res
 * @param next
 */
function requireFilesAvailable(req, res, next) {
  req.$files = req.$compilingFiles; // 只有可解析的文件才能执行、生成测试用例

  const promises = [];
  if (req.$functions) {
    req.$functions.forEach((func) => {
      promises.push(files.getCompilingFunction(func)); // 只有可解析的函数才能执行、生成测试用例
    });
  }
  Promise.all(promises)
    .then((functions) => {
      req.$functions = functions.filter((f) => f);
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取文件列表
 * @param req
 * @param res
 * @param next
 */
function getFiles(req, res, next) {
  const { projectVersionId } = req.params;
  const query = {
    extensions: req.query.extensions || req.body.extensions,
    status: req.query.fileStatus || req.body.fileStatus,
  };
  query.kinds = [files.kind.file];
  if (req.query.functionIds && !req.query.fileIds) {
    req.query.fileIds = [];
  }
  const { force } = req.body;
  files.getFiles(projectVersionId, req.query, query)
    .then((resFiles) => {
      req.$files = resFiles;
      if (force) {
        // 强制重新解析
        req.$compilingFiles = resFiles;
      } else {
        // 排除解析成功和解析失败的文件后是目前可解析的文件
        // eslint-disable-next-line max-len
        req.$compilingFiles = req.$files.filter((f) => f.status !== files.statusKind.compiled && f.status !== files.statusKind.compileFailed);
      }
      // eslint-disable-next-line max-len
      req.$compiledFiles = req.$files.filter((f) => f.status === files.statusKind.compiled || f.status === files.statusKind.compileFailed);
      req.$notCompiledFiles = req.$files.filter((f) => f.status === files.statusKind.notCompiled);
      next();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取文件信息
 * @param req
 * @param res
 * @param next
 */
function getFile(req, res, next) {
  try {
    const { extensions, status } = req.query;
    const file = files.getJson(req.$file, extensions, status);
    if (!file.encoding) {
      file.encoding = req.$projectVersion.encoding || 'uft-8';
    }
    res.json(file);
  } catch (err) {
    next(new PhoenixError(err));
  }
}

function getFileContent(req, res, next) {
  (async () => {
    const encoding = req.$file.encoding || req.$projectVersion.encoding || 'uft-8';
    res.set({
      'Content-Type': `text/plain; charset=${encoding}`,
    });
    const stream = await files.getFileContentStream(req.params.fileId, req.$file);
    stream.on('data', (chunk) => {
      res.write(chunk);
    });
    stream.on('end', () => res.end());
  })()
    .catch(next);
}

/**
 * 获取子目录/文件列表
 * @param req
 * @param res
 * @param next
 */
function getFileChildren(req, res, next) {
  const resFiles = {
    total: 0,
  };
  const { extensions, status } = req.query;
  files.getChildren(req.params.fileId, req.query)
    .then((children) => {
      resFiles.total = children.length;
      resFiles.files = children.map((file) => files.getSummaryJson(file, extensions, status));
      res.json(resFiles);
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取目录树结构
 * @param req
 * @param res
 * @param next
 */
function getFileTree(req, res, next) {
  let depth = null;
  if (req.query.depth) {
    depth = Number(req.query.depth);
  }
  if (req.query.level) {
    req.query.level = utility.getObjectKeyByValue(
      rules.ruleLevel,
      Number(req.query.level)
    );
  }
  const {
    extensions,
    status,
  } = req.query;
  const resFiles = { total: 1 };
  resFiles.tree = files.getSummaryJson(req.$file, extensions, status);
  resFiles.tree.children = [];
  if (req.$file.kind === files.kind.directory) {
    if (extensions !== undefined && utility.isEmpty(extensions)) {
      res.json(resFiles);
      return Promise.resolve();
    }
    if (depth === 1 && ['hasCodeDefect', 'testResult', 'status'].filter((key) => req.query[key] !== undefined).length === 0) {
      return files.getChildren(req.params.fileId, req.query)
        .then((children) => {
          resFiles.total += children.length;
          resFiles.tree.children = children.map(
            (file) => files.getSummaryJson(file, extensions, status)
          );
          res.json(resFiles);
        }).catch((err) => {
          next(new PhoenixError(err));
        });
    }
    // TODO need to optimize the performance
    return files.getTree(req.params.fileId, req.query)
      .then((tree) => {
        function addChildren(rootJson, rootFile, depth) {
          if (rootFile.kind === files.kind.directory && !rootJson.children) {
            rootJson.children = [];
          }
          if (depth !== null) {
            depth -= 1;
            if (depth < 0) {
              return;
            }
          }
          if (!rootFile.children) {
            return;
          }
          rootFile.children.forEach((childFile) => {
            const childJson = files.getSummaryJson(childFile, extensions, status);
            resFiles.total += 1;
            rootJson.children.push(childJson);
            addChildren(childJson, childFile, depth);
          });
        }
        if (tree) {
          addChildren(resFiles.tree, tree, depth);
        }
        res.json(resFiles);
      })
      .catch((err) => {
        next(new PhoenixError(err));
      });
  }
  // get tree of file elements
  return fileElements.getFileElementsTree(req.params.fileId, req.params.fileId, 0, depth, req.query)
    .then((tree) => {
      resFiles.tree.children = tree.tree;
      resFiles.total += tree.total;
      res.json(resFiles);
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取目录树结构(包含函数)
 * @param req
 * @param res
 * @param next
 */
function getFileTreeAll(req, res, next) {
  return files.getTreeV2(req.params.fileId, req.query, req.$projectVersion)
    .then((resFiles) => {
      res.json(resFiles);
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取目录树结构(包含缺陷)
 * @param req
 * @param res
 * @param next
 */
function getFileDefectTree(req, res, next) {
  const { level } = req.query;
  const levels = level ? [].concat(level).map((l) => parseInt(l, 10)) : undefined;
  const {
    extensions,
    status,
  } = req.query;
  const resFiles = { total: 1 };
  resFiles.tree = files.getSummaryJson(req.$file, extensions, status);
  resFiles.tree.children = [];
  const fileIds = [];
  return Promise.resolve()
    .then(() => {
      if (req.$file.kind === files.kind.directory) {
        if (extensions !== undefined && utility.isEmpty(extensions)) {
          return Promise.resolve();
        }
        return files.getTree(req.params.fileId, req.query)
          .then((tree) => {
            function addChildren(rootJson, rootFile) {
              if (rootFile.kind === files.kind.file) {
                fileIds.push(rootFile._id);
                return;
              }
              if (rootFile.kind === files.kind.directory && !rootJson.children) {
                rootJson.children = [];
              }
              if (!rootFile.children) {
                return;
              }
              rootFile.children.forEach((childFile) => {
                const childJson = files.getSummaryJson(childFile, extensions, status);
                resFiles.total += 1;
                rootJson.children.push(childJson);
                addChildren(childJson, childFile);
              });
            }
            if (tree) {
              addChildren(resFiles.tree, tree);
            }
          });
      }
      fileIds.push(req.params.fileId);
      return Promise.resolve();
    })
    .then(() => {
      if (fileIds.length === 0) {
        return Promise.resolve();
      }
      return defects.getFilesRuleDefectsCount(fileIds, {
        levels,
        versionId: req.$projectVersion._id,
      })
        .then((ret) => {
          const fileDefectMap = new Map();
          ret.forEach((defect) => {
            if (!fileDefectMap.has(String(defect.fileId))) {
              fileDefectMap.set(String(defect.fileId), []);
            }
            fileDefectMap.get(String(defect.fileId)).push({ ruleId: defect.ruleId, total: defect.total, totalByStatus: defect.totalByStatus });
          });
          function addFileDefects(rootFile) {
            if (rootFile.kind === files.kind.file) {
              rootFile.fileDefects = fileDefectMap.has(String(rootFile.fileId)) ? fileDefectMap.get(String(rootFile.fileId)) : [];
              return;
            }
            if (!rootFile.children) {
              return;
            }
            rootFile.children.forEach((childFile) => {
              addFileDefects(childFile);
            });
          }
          addFileDefects(resFiles.tree);
          // 计算文件树各个层级的文件的defectsCountByStatus
          function computeDefectsCountByStatus(tree) {
            tree.defectsCountByStatus = tree.defectsCountByStatus || {};
            if (tree.kind === files.kind.file) {
              // file
              tree.defectsCountByStatus = {};
              if (tree.fileDefects && tree.fileDefects.length) {
                tree.fileDefects.forEach((item) => utility.mergeAdd(tree.defectsCountByStatus, item.totalByStatus));
              }
            } else if (tree.children && tree.children.length) {
              // directory
              tree.children.forEach((child) => {
                utility.mergeAdd(tree.defectsCountByStatus, computeDefectsCountByStatus(child));
              });
            }
            return tree.defectsCountByStatus;
          }
          computeDefectsCountByStatus(resFiles.tree);
        });
    })
    .then(() => {
      res.json(resFiles);
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 添加文件/文件夹
 * @param req
 * @param res
 * @param next
 */
function addFile(req, res, next) {
  req.$addFile = true;
  const { type = files.repositoryType.file, url } = req.query;
  let { kind, fileName } = req.query;
  if (!kind) {
    next(new PhoenixError('invalid request', 'Missing parameters. "kind" is required'));
    return;
  }

  kind = Number.parseInt(kind.toString(), 10);
  switch (Number.parseInt(type.toString(), 10)) {
    case files.repositoryType.file:
      if (!fileName && !req.file) {
        next(new PhoenixError('invalid request', 'Missing parameters. "fileName" or "file" is required'));
        return;
      }
      if (!fileName) {
        if (kind === files.kind.file) {
          fileName = req.file.originalname;
        } else {
          fileName = path.basename(req.file.originalname, path.extname(req.file.originalname));
        }
      }
      break;
    case files.repositoryType.git:
      if (!url) {
        next(new PhoenixError('invalid request', 'Missing parameters. "url" is required'));
        return;
      }
      if (kind === files.kind.file) {
        next(new PhoenixError('over range', 'File kind can not be file'));
        return;
      }
      if (!fileName) {
        fileName = gitUrlParse(url).name;
      }
      break;
    default:
      next(new PhoenixError('invalid request', `Invalid parameters. Unable to handle the repository type: ${type}`));
      break;
  }

  const {
    $file,
    $projectVersion,
    $project,
  } = req;
  const versionId = $projectVersion._id; // eslint-disable-line no-underscore-dangle
  let file;
  files.getFileByPath(versionId, path.join($file.path, fileName))
    .then((file) => {
      if (file) {
        return Promise.reject(new PhoenixError('conflict', 'The file exists'));
      }
      const { fileId } = req.params;
      const fileInfo = { kind, fileName };
      return files.addFile2Directory(fileId, fileInfo, $file, $projectVersion, $project, $project.creator);
    })
    .then((res) => {
      file = res;
      const { addFile } = projectVersionHistories.kind;
      return projectVersionHistories.addHistory(versionId, addFile, { filePath: res.path });
    })
    .then(() => {
      if (req.file || url) {
        req.query.type = type;
        req.$file = file;
        req.params.fileId = file._id; // eslint-disable-line no-underscore-dangle
        next();
      } else {
        res.json(files.getJson(file));
      }
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 批量添加文件
 * @param req
 * @param res
 * @param next
 */
function addFiles(req, res, next) {
  if (!req.files) {
    next(new PhoenixError('invalid request', 'Missing parameters. "files" is required'));
    return;
  }

  const {
    $file,
    $projectVersion,
    $project,
  } = req;
  const zipFileNameEncoding = req.body.zipFileNameEncoding || 'gb2312';
  const versionId = $projectVersion._id; // eslint-disable-line no-underscore-dangle
  const promises = req.files.map((file) => {
    const filePath = path.join($file.path, file.originalname);
    return files.getFileByPath(versionId, filePath);
  });
  Promise.all(promises)
    .then((resFiles) => {
      let err;
      resFiles.forEach((file) => {
        if (file) {
          err = new PhoenixError('conflict', 'The file exists');
        }
      });
      if (err) {
        return Promise.reject(err);
      }
      const { fileId } = req.params;
      return files.addFiles(fileId, req.files, $file, $projectVersion, $project, $project.creator, zipFileNameEncoding);
    })
    .then(() => {
      const { addFile } = projectVersionHistories.kind;
      const promises = [];
      req.files.forEach((file) => {
        const filePath = path.join($file.path, file.originalname);
        promises.push(projectVersionHistories.addHistory(versionId, addFile, { filePath }));
      });
      return Promise.all(promises);
    })
    .then(() => checkEnvironmentChanged(req))
    .then(() => projectVersions.updateRegressionStatusPrompt(versionId, 'update'))
    .then(() => {
      res.json();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 更新文件/文件夹
 * @param req
 * @param res
 * @param next
 */
function updateFiles(req, res, next) {
  const {
    query,
    file,
    $file,
    $projectVersion,
    $project,
  } = req;
  const { type, url } = query;
  const fileId = $file._id; // eslint-disable-line no-underscore-dangle
  if (!type) {
    next(new PhoenixError('invalid request', 'Missing parameters. "type" is required'));
    return;
  }
  if (req.query.password) { req.query.password = null; }
  if (req.body.password) { req.query.password = req.body.password; }
  let promise;
  let filePath = $file.path;
  const zipFileNameEncoding = query.zipFileNameEncoding || 'gb2312';
  const uploadType = Number.parseInt(type.toString(), 10);
  switch (uploadType) {
    case files.repositoryType.git:
    case files.repositoryType.svn:
      if ($file.kind === files.kind.file) {
        promise = Promise.reject(new PhoenixError('over range', 'File kind can not be file'));
      } else if (!url) {
        promise = Promise.reject(new PhoenixError('invalid request', 'Missing parameters. "url" is required'));
      } else {
        promise = files.updateByRemoteSource(
          fileId,
          url,
          query,
          $file,
          $projectVersion,
          $project,
          $project.creator,
          uploadType
        );
      }
      break;
    case files.repositoryType.file:
      if (file && $file.path !== '.') { // 顶层目录跳过判断，因为有bug: path.dirname('.') 等于 '.'
        files
          .getFileByPath($file.versionId, path.join(path.dirname($file.path), file.originalname))
          .then((fileExists) => {
            if (fileExists && fileExists._id.toString() !== $file._id.toString()) {
              next(new PhoenixError('conflict', `${file.originalname} exists`));
            }
          });
      }
      if (!file && !url) {
        promise = Promise.reject(new PhoenixError('invalid request', 'Missing parameters. "url" or "file" is required'));
      } else if (url) {
        // 设置远程文件
        promise = files.updateByRemoteFile(fileId, url, $file, $projectVersion, $project, $project.creator, zipFileNameEncoding);
      } else if ($file.path === '.') {
        // 根目录上传文件
        const { path, isDir, originalname } = file;
        const prefix = isDir ? originalname : '';
        promise = files.updateByUploadFile(fileId, path, $file, $projectVersion, $project, $project.creator, null, zipFileNameEncoding, prefix);
      } else {
        // 替换其他文件
        let name = $file.fileName;
        if (!req.$addFile) {
          name = file.originalname;
          if ($file.kind === files.kind.directory) {
            name = path.join(path.dirname(name), path.basename(name, path.extname(name)));
          }
        }
        // 目录上传的话，保留原始目录层级
        const prefix = file.isDir ? file.originalname : '';
        const v = $projectVersion;
        filePath = path.join(path.dirname($file.path), name);
        promise = files.updateByUploadFile(fileId, file.path, $file, v, $project, $project.creator, name, undefined, prefix);
      }
      break;
    default:
      promise = Promise.reject(new PhoenixError('invalid request', `Invalid parameters. Unable to handle the repository type: ${type}`));
      break;
  }

  let resFile;
  promise
    .then(() => files.getFileByPath($file.versionId, filePath))
    .then((updatedFile) => {
      if (updatedFile.extra) {
        return res.json(files.getJson(updatedFile));
      }
      resFile = updatedFile;
      return files.getFileContent(resFile._id, resFile)
        .then((content) => {
          resFile.content = content;
          if (req.$addFile) {
            return Promise.resolve();
          }
          let historyKind;
          let details;
          if ($file.path === '.') {
            // 根目录，源码更新
            historyKind = projectVersionHistories.kind.updateSource;
          } else {
            historyKind = projectVersionHistories.kind.replaceFile;
            details = {
              originFilePath: $file.path,
              newFilePath: resFile.path,
            };
          }
          return projectVersionHistories.addHistory($file.versionId, historyKind, details);
        })
        .then(() => {
          if ($file.path !== '.') {
            return Promise.resolve();
          }
          return defects.clearDefects($file.versionId);
        })
        .then(() => checkEnvironmentChanged(req))
        .then(() => projectVersions.updateRegressionStatusPrompt($file.versionId, 'update'))
        .then(() => {
          res.json(files.getJson(resFile));
        });
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 删除文件/文件夹
 * @param req
 * @param res
 * @param next
 */
function removeFile(req, res, next) {
  const {
    $file,
    $projectVersion,
    $project,
  } = req;
  files.removeFile(req.params.fileId, $file, $projectVersion, $project, $project.creator)
    .then(() => {
      const { versionId } = $file;
      const { deleteFile } = projectVersionHistories.kind;
      return projectVersionHistories.addHistory(versionId, deleteFile, { filePath: $file.path });
    })
    .then(() => checkEnvironmentChanged(req))
    .then(() => projectVersions.updateRegressionStatusPrompt($file.versionId, 'update'))
    .then(() => {
      res.status(204).json();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 编辑文件/文件夹
 * @param req
 * @param res
 * @param next
 */
function editFile(req, res, next) {
  let { fileId } = req.params;
  const versionId = req.$projectVersion._id; // eslint-disable-line no-underscore-dangle
  const doc = {
    content: req.body.content,
  };
  let file;
  Promise.resolve()
    .then(() => {
      if ((doc.content || doc.content === '') && req.$file.kind !== files.kind.file) {
        return Promise.reject(new PhoenixError('over range', 'Can not edit content of directory'));
      }
      return Promise.resolve();
    })
    .then(() => {
      if (!req.query.path && !req.query.fileName) {
        return Promise.resolve();
      }
      doc.target = req.$file.path;
      if (doc.target === '.') {
        return Promise.reject(new PhoenixError('forbidden', 'Unable to move "."'));
      }
      if (req.query.path) {
        doc.target = req.query.path;
      }
      if (req.query.fileName) {
        doc.target = path.join(path.dirname(doc.target), req.query.fileName);
      }
      if (path.isAbsolute(doc.target)) {
        return Promise.reject(new PhoenixError('forbidden', 'Path out of range'));
      }
      if (path.relative('.', doc.target).startsWith('..')) {
        return Promise.reject(new PhoenixError('forbidden', 'Path out of range'));
      }
      if (doc.target === req.$file.path) {
        delete doc.target;
      }

      if (doc.target) {
        return files.getFileByPath(versionId, doc.target);
      }
      return Promise.resolve();
    })
    .then((file) => {
      if (doc.target && file) {
        return Promise.reject(new PhoenixError('conflict', 'target is exists'));
      }
      const {
        $file,
        $projectVersion,
        $project,
      } = req;
      return files.updateFile(fileId, doc, $file, $projectVersion, $project, $project.creator);
    })
    .then(() => {
      if (doc.target) {
        const versionId = req.$projectVersion._id; // eslint-disable-line no-underscore-dangle
        return files.getFileByPath(versionId, doc.target);
      }
      return Promise.resolve();
    })
    .then((file) => {
      if (file) {
        fileId = file._id; // eslint-disable-line no-underscore-dangle
      }
      return files.getFile(fileId);
    })
    .then((res) => {
      file = res;
      return files.getFileContent(fileId, file);
    })
    .then((content) => {
      file.content = content;
      if (doc.content === undefined) {
        return Promise.resolve();
      }
      const { editFile } = projectVersionHistories.kind;
      return projectVersionHistories.addHistory(versionId, editFile, { filePath: file.path });
    })
    .then(() => projectVersions.updateRegressionStatusPrompt(versionId, 'update'))
    .then(() => checkEnvironmentChanged(req))
    .then(() => {
      res.json(files.getJson(file));
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * Export model definition object.
 */
module.exports = {
  requireFileExist,
  requireDirectory,
  requireFileAvailable,
  filterFilesAvailable: requireFilesAvailable,

  parseFileListQuery,
  getFiles,

  getFile,
  getFileChildren,
  getFileTree,
  getFileTreeAll,
  getFileDefectTree,
  addFile,
  addFiles,
  updateFiles,
  removeFile,
  editFile,
  getFileContent,
};
